#include <click/config.h>
#include "igmpstate.hh"
#include <click/algorithm.hh>
#include <sys/types.h>
#include <set>
#include <cassert>

#include <click/vector.cc>
template class Vector<in_addr>;

CLICK_DECLS

bool operator!=(const in_addr& addr1, const in_addr& addr2) {
    return (addr1.s_addr != addr2.s_addr);
}

bool operator==(const in_addr& addr1, const in_addr& addr2) {
    return (addr1.s_addr == addr2.s_addr);
}

///sourceList = sourceList + mergeSourceList
bool unionSourceList(Vector<in_addr>& sourceList, const Vector<in_addr>& mergeSourceList) {
    //take union of source lists
    bool changed = false;
    for (Vector<in_addr>::const_iterator it = mergeSourceList.begin(); it != mergeSourceList.end(); ++it ) {
       if (find(sourceList.begin(), sourceList.end(), *it) == sourceList.end() ) {
            changed = true;
            sourceList.push_back(*it);
        }
    }
    return changed;
}
 
///sourceList = sourceList - mergeSourceList
bool differenceSourceList(Vector<in_addr>& sourceList, const Vector<in_addr>& mergeSourceList) {
    //take diference of source lists
    bool changed = false;
    for (Vector<in_addr>::const_iterator it = mergeSourceList.begin(); it != mergeSourceList.end(); ++it ) {
        Vector<in_addr>::iterator toRemove = find(sourceList.begin(), sourceList.end(), *it);
        if ( toRemove != sourceList.end()) {
            changed = true;
            sourceList.erase(toRemove);
        }
    }
    return changed;
}

IGMPState::IGMPState(const in_addr& multicastAddress) 
  :mIsSourceListSorted(false)
  ,mMulticastAddress(multicastAddress)
  ,mFilterMode(EXCLUDE)
  ,mSourceList(Vector<in_addr>())
{}

IGMPState::IGMPState(const in_addr& multicastAddress, const IGMPFilterMode& filterMode, const Vector<in_addr>& sourceList) 
  :mIsSourceListSorted(false)
  ,mMulticastAddress(multicastAddress)
  ,mFilterMode(filterMode)
  ,mSourceList(sourceList)
{}

IGMPState::IGMPState(const in_addr& multicastAddress, const IGMPFilterMode& filterMode, int numberOfSources, in_addr* sourceList) 
  :mIsSourceListSorted(false)
  ,mMulticastAddress(multicastAddress)
  ,mFilterMode(filterMode)
{
    for (int i = 0; i < numberOfSources; ++i) {
        mSourceList.push_back(sourceList[i]);
    }
}

IGMPState::IGMPState (const struct IGMPGroupRecord* groupRecord)
  :mIsSourceListSorted(false)
  ,mMulticastAddress(groupRecord->multicastAddress)
{
    mFilterMode = (IGMPFilterMode)groupRecord->recordType;
    for (int i = 0; i < ntohs(groupRecord->numberOfSources); ++i) {
        mSourceList.push_back(groupRecord->sourceAddressList[i]);
    }
}

IGMPState::~IGMPState () {}

int IGMPState::getGroupRecordSize() const {
    return (sizeof(IGMPGroupRecord) + mSourceList.size()*sizeof(in_addr));
}

void IGMPState::copyGroupRecord(struct IGMPGroupRecord* groupRecord) const {
    groupRecord->recordType = uint8_t(mFilterMode);
    groupRecord->auxDataLength = 0;
    groupRecord->numberOfSources = htons(mSourceList.size());
    groupRecord->multicastAddress = mMulticastAddress;
    if (mSourceList.size() > 0)
        memcpy(groupRecord->sourceAddressList, &mSourceList[0], mSourceList.size()*sizeof(in_addr));
}

bool IGMPState::merge(const IGMPState& mergeState) {
    assert(mMulticastAddress == mergeState.mMulticastAddress);
    bool changed = false;
    const Vector<in_addr>& mergeSourceList = mergeState.mSourceList;
    
    if (mFilterMode == INCLUDE && mergeState.mFilterMode == INCLUDE) {
        changed = unionSourceList(mSourceList, mergeSourceList);      
    } else if (mFilterMode == EXCLUDE && mergeState.mFilterMode == EXCLUDE) {
        changed = differenceSourceList(mSourceList, mergeSourceList);
    } else if (mFilterMode == INCLUDE && mergeState.mFilterMode == EXCLUDE) {
        mFilterMode = EXCLUDE;
        changed = true;
        const Vector<in_addr> oldSourceListold = mSourceList;
        mSourceList = mergeSourceList;
        differenceSourceList(mSourceList, oldSourceListold);
    } else if (mFilterMode == EXCLUDE && mergeState.mFilterMode == INCLUDE) {
        changed = differenceSourceList(mSourceList, mergeSourceList);
    }
    if (changed == true) {
        mIsSourceListSorted = false;
    }
    return changed;
}

bool IGMPState::accept(const in_addr sourceAddress) const {
    if (((mFilterMode == EXCLUDE) && (find(mSourceList.begin(), mSourceList.end(), sourceAddress) == mSourceList.end()))
      || ((mFilterMode == INCLUDE) && (find(mSourceList.begin(), mSourceList.end(), sourceAddress) != mSourceList.end()))) {
        return true; 
    } else {
        return false;
    }
}

bool IGMPState::isSuperSetOf(const IGMPState& receivedState) const {
    assert(mMulticastAddress == receivedState.mMulticastAddress);
    if ((mFilterMode == EXCLUDE) && (receivedState.mFilterMode == INCLUDE)) {
        return false;
    } else if ((mFilterMode == EXCLUDE) && (receivedState.mFilterMode == EXCLUDE)) {
        Vector<in_addr> tempSourceList = receivedState.mSourceList;
        differenceSourceList(tempSourceList, mSourceList); //tempSourceList = receivedState.mSourceList - mSourceList
        if (tempSourceList.size() == 0) {
            return true;
        } else {
            return false;
        }
    } else if ((mFilterMode == INCLUDE) && (receivedState.mFilterMode == INCLUDE)) {
        Vector<in_addr> tempSourceList = mSourceList;
        differenceSourceList(tempSourceList, receivedState.mSourceList); //tempSourceList = mSourceList - receivedState.mSourceList 
        if (tempSourceList.size() == 0) {
            return true;
        } else {
            return false;
        }                 
    } else { //((mFilterMode == INCLUDE) && (receivedState.mFilterMode == EXCLUDE))
        Vector<in_addr> tempSourceList = mSourceList;
        differenceSourceList(tempSourceList, receivedState.mSourceList); //tempSourceList = mSourceList - receivedState.mSourceList 
        if (tempSourceList.size() == mSourceList.size()) {
            return true;
        } else {
            return false;
        }     
    }
}


CLICK_ENDDECLS
ELEMENT_REQUIRES(userlevel|ns|vector)
ELEMENT_PROVIDES(IGMPState)
